#include <pthread.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include "ptwrap.hpp"

// [[ex02aa]]
// DEBUG descr The signal handling in a dedicated thread.
class sighandler_thread : public pw::pwthread
{
public:
	sighandler_thread() :
		done_(false)
	{ 
		sigemptyset(&mask_);
		// add all the handled signals to the mask
		sigaddset(&mask_, SIGCLD);
		sigaddset(&mask_, SIGUSR1);
		// ...

		sigemptyset(&pending_);
	}

	void *execute()
	{
		sigset_t empty, handle;
		sigemptyset(&empty);

		pthread_sigmask(SIG_BLOCK, &mask_, &handle);

		// DEBUG {
		fprintf(stderr, "Signal thread is %p\n", (void *)get_id());
		// DEBUG }
		while (true) {
			{
				pw::lockmutex lm(cond_);
				while(!done_ && !memcmp(&pending_, 
						&empty, sizeof(sigset_t)))
					cond_.wait();
				if (done_)
					return 0;

				handle = pending_;
				sigemptyset(&pending_);
			}

			// now handle the signals
			if (sigismember(&handle, SIGCLD)) {
				// ...
				// DEBUG {
				fprintf(stderr, "Handled SIGCLD\n");
				// DEBUG }
			}
			if (sigismember(&handle, SIGUSR1)) {
				// ...
				// DEBUG {
				fprintf(stderr, "Handled SIGUSR1\n");
				// DEBUG }
			}
			// ...
		}
		return 0;
	}

	void schedule(int signo)
	{
		// DEBUG {
		// printing from a signal handler is generally unsafe, but the
		// test example makes sure that it doesn't happen an a wrong time
		fprintf(stderr, "Scheduled signal %d from thread %p\n", signo, (void *)pthread_self());
		// DEBUG }
		pw::lockmutex lm(cond_);
		sigaddset(&pending_, signo);
		cond_.signal();
	}

	void stop()
	{
		pw::lockmutex lm(cond_);
		done_ = true;
		cond_.signal();
	}

	sigset_t getmask()
	{
		return mask_;
	}

protected:
	sigset_t mask_;
	sigset_t pending_;
	bool done_;
	pw::pmcond cond_;
};

sighandler_thread *sigthread;
pw::pmutex sigmutex;

void handler(int signo)
{
	pw::lockmutex lm(sigmutex);
	if (sigthread)
		sigthread->schedule(signo);
}

// DEBUG {
class signaler_thread : public pw::pwthread
{
public:
	signaler_thread(int signo, int delay) :
		signo_(signo), delay_(delay)
	{ }

	void *execute()
	{
		fprintf(stderr, "Signaler %d delay %d  thread is %p\n", signo_, delay_, (void *)pthread_self());
		sleep(delay_);
		kill(getpid(), signo_);
		return 0;
	}

protected:
	int signo_;
	int delay_;
};

// DEBUG }
int main(int argc, char **argv)
{
	// ... other initialization ...
	// DEBUG {
	fprintf(stderr, "Main thread is %p\n", (void *)pthread_self());
	// DEBUG }

	// initialize the signals
	sigthread = new sighandler_thread();
	sigthread->start();

	struct sigaction sa;
	sa.sa_handler = handler;
	sa.sa_mask = sigthread->getmask();
	sa.sa_flags = 0;
	sigaction(SIGCLD, &sa, 0); 
	sigaction(SIGUSR1, &sa, 0); 
	// ...

	// ... run and then join all the other threads ...
	
	// DEBUG {
	pw::pwthread *s1 = new signaler_thread(SIGUSR1, 1);
	pw::pwthread *s2 = new signaler_thread(SIGCLD, 2);
	s1->start(); s2->start();
	s1->join(); s2->join();
	delete s1; delete s2;
	sleep(1);

	// DEBUG }
	// disable the handlers
	sa.sa_handler = SIG_IGN;
	sigaction(SIGCLD, &sa, 0); 
	sigaction(SIGUSR1, &sa, 0); 
	// ...

	// finally stop the handler thread
	{
		sighandler_thread *t = sigthread;
		{
			pw::lockmutex lm(sigmutex);
			sigthread = 0;
		}
		t->stop();
		t->join();
		delete t;
	}

	return 0;
}
// END

/**
Sample output:

Main thread is 0x7f93fe282710
Signal thread is 0x7f93fe281910
Signaler 17 delay 2  thread is 0x7f93fce7f910
Signaler 10 delay 1  thread is 0x7f93fd880910
Scheduled signal 10 from thread 0x7f93fe282710
Handled SIGUSR1
Scheduled signal 17 from thread 0x7f93fe282710
Handled SIGCLD
*/
